﻿// Copyright 2012 Jack Bulan (agentgeo)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace GemWorkshop.Formats.Nitro
{
    public class Narc
    {
        private const uint NARC = 0x4352414E;
        private const uint FATB = 0x46415442;
        private const uint FNTB = 0x464E5442;
        private const uint FIMG = 0x46494D47;

        public Dictionary<uint, uint> FileOffsets;
        public Dictionary<uint, uint> FileSizes;
        private uint fileCount;
        private uint fileDataOffset;

        public string filePath;

        private uint fatbOffset;

        public Narc(string path)
        {
            FilePath = path;
            Load();
        }

        // Used to load the file
        private void Load()
        {
            // Initialize
            FileOffsets = new Dictionary<uint, uint>();
            FileSizes = new Dictionary<uint, uint>();
            BinaryReader br = new BinaryReader(File.OpenRead(FilePath));

            // Read header
            if (br.ReadUInt32() != NARC)
            {
                throw new Exception();
            }

            // Skip format header
            br.BaseStream.Seek(4L, SeekOrigin.Current);
            // Read rest of header
            uint sectionSize = br.ReadUInt32();
            ushort headerSize = br.ReadUInt16();
            ushort subSections = br.ReadUInt16();

            // Read file allocation table
            if (br.ReadUInt32() != FATB)
            {
                throw new Exception();
            }
            uint fatSize = br.ReadUInt32();

            FileCount = br.ReadUInt32();
            for (uint iFile = 0; iFile < FileCount; iFile++)
            {
                uint FileStart = br.ReadUInt32();
                uint FileEnd = br.ReadUInt32();

                FileOffsets[iFile] = FileStart;
                FileSizes[iFile] = (FileEnd - FileStart);
            }

            // Read/skip the file name table
            if (br.ReadUInt32() != FNTB)
            {
                throw new Exception();
            }
            uint fntSize = br.ReadUInt32();
            br.BaseStream.Seek(fntSize - 8, SeekOrigin.Current);
            // TODO: See if we can load this???

            if (br.ReadUInt32() != FIMG)
            {
                throw new Exception();
            }
            uint fimgSize = br.ReadUInt32();

            // Where file data starts at
            FileDataOffset = (uint)br.BaseStream.Position;

            // Finish up.
            br.Close();
            br.Dispose();
        }

        /// <summary>
        /// Extracts the specified file from the archive.
        /// </summary>
        /// <param name="FileId">The number of the file to be extracted.</param>
        /// <returns></returns>
        public byte[] ExtractFile(uint FileId)
        {
            BinaryReader br = new BinaryReader(File.OpenRead(FilePath));

            br.BaseStream.Seek((long)(FileDataOffset + FileOffsets[FileId]), SeekOrigin.Begin);
            byte[] buffer = br.ReadBytes((int)FileSizes[FileId]);

            br.Close();
            br.Dispose();
            return buffer;
        }

        /// <summary>
        /// Extracts the specified file from the archive to a MemoryStream.
        /// </summary>
        /// <param name="FileId">The number of the file to be extracted.</param>
        /// <returns></returns>
        public MemoryStream ExtractStream(uint FileId)
        {
            return new MemoryStream(ExtractFile(FileId));
        }

        public void ReplaceFile(uint FileId, byte[] Buffer)
        {
            if (Buffer.Length > FileSizes[FileId])
            {
                uint diff = (uint)Buffer.Length - FileSizes[FileId];
                uint endOffset = FileOffsets[FileId] + FileSizes[FileId];

                //MessageBox.Show("File is larger!\nDifference: " + diff + " bytes");

                BinaryReader br = new BinaryReader(File.OpenRead(FilePath));
                
                // Save
                byte[] before = br.ReadBytes((int)FileOffsets[FileId]); // Read up to
                br.BaseStream.Position = endOffset;
                byte[] after = br.ReadBytes((int)(br.BaseStream.Length - endOffset));

                br.Close();
                br.Dispose();

                BinaryWriter bw = new BinaryWriter(File.OpenWrite(FilePath));
                
                //bw.BaseStream.Seek(endOffset, SeekOrigin.Begin);
                //byte[] after = new byte[bw.BaseStream.Length - endOffset]; // Save for later
                //bw.BaseStream.Read(after, 0, after.Length);
                bw.Write(before); // Everything up to...
                bw.Write(Buffer); // The new file data!
                bw.Write(after);  // Everything following...

                // Update file entries
                uint uDiff = (uint)diff;
                FileSizes[FileId] = (uint)Buffer.Length;
                for (uint u = FileId + 1; u < FileCount; u++)
                {
                    FileOffsets[u] += uDiff;
                    FileSizes[u] += uDiff;
                }

                // Write the file entries
                bw.BaseStream.Seek(0x10 + (FileId * 8), SeekOrigin.Begin); // Seek write start
                for (uint u = FileId; u < FileCount; u++)
                {
                    bw.Write(FileOffsets[u]);
                    uint end = FileOffsets[u] + FileSizes[u];
                    bw.Write(end);
                }

                bw.Close();
                bw.Dispose();
            }
            else
            {
                BinaryWriter bw = new BinaryWriter(File.OpenWrite(FilePath));

                long writeTo = (long)(FileDataOffset + FileOffsets[FileId]);
                bw.BaseStream.Seek(writeTo, SeekOrigin.Begin);
                bw.Write(Buffer);


                bw.Close();
                bw.Dispose();
            }
            // TODO: Longer length files

        }

        public void ReplaceFile(uint FileId, MemoryStream Stream)
        {
            ReplaceFile(FileId, Stream.ToArray());
        }

        /// <summary>
        /// Returns a stream containing this archive.
        /// </summary>
        /// <returns></returns>
        public FileStream ToStream()
        {
            return File.Open(FilePath, FileMode.Open,
                FileAccess.ReadWrite, FileShare.ReadWrite);
        }

        #region Properties

        public uint FileCount
        {
            get { return fileCount; }
            set { fileCount = value; }
        }

        public uint FileDataOffset
        {
            get { return fileDataOffset; }
            set { fileDataOffset = value; }
        }

        public string FilePath
        {
            get { return filePath; }
            set { filePath = value; }
        }

        #endregion

        // TODO: Is there anything more to do???
    }
}
